package com.zyc.fuckfx.apk;

import com.zyc.fuckfx.ImpliedFeature;
import com.zyc.fuckfx.base.Log;
import org.apache.commons.codec.digest.DigestUtils;

import java.io.*;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

/**
 * @Author AlbertZyc
 * @Date 2022/8/13
 * @Description 自己悟出来
 */
public class ApkUtil {
    public static final String VERSION_CODE = "versionCode";
    public static final String VERSION_NAME = "versionName";
    public static final String SDK_VERSION = "sdkVersion";
    public static final String TARGET_SDK_VERSION = "targetSdkVersion";
    public static final String USES_PERMISSION = "uses-permission";
    public static final String APPLICATION_LABEL = "application-label";
    public static final String APPLICATION_ICON = "application-icon";
    public static final String USES_FEATURE = "uses-feature";
    public static final String USES_IMPLIED_FEATURE = "uses-implied-feature";
    public static final String SUPPORTS_SCREENS = "supports-screens";
    public static final String SUPPORTS_ANY_DENSITY = "supports-any-density";
    public static final String DENSITIES = "densities";
    public static final String PACKAGE = "package";
    public static final String nativeCode = "native-code";
    public static final String APPLICATION = "application:";
    public static final String LAUNCHABLE_ACTIVITY = "launchable-activity";

    private ProcessBuilder mBuilder;
    private static final String SPLIT_REGEX = "(: )|(=')|(' )|'";
    private static final String FEATURE_SPLIT_REGEX = "(:')|(',')|'";
    /**
     * aapt所在的目录。
     */
    //windows环境下直接指向appt.exe
    private String mAaptPath = "src/main/resources/lib/aapt";
    //linux下
    private String mMacAaptPath = "src/main/resources/lib/mac/aapt";

    public ApkUtil() {
        mBuilder = new ProcessBuilder();

        mBuilder.redirectErrorStream(true);
    }

    public static void setExecutable(File file, boolean executable) throws IOException {
        Process p = Runtime.getRuntime().exec(new String[]{
                "chmod",
                "u" + (executable ? '+' : '-') + "x",
                file.getAbsolutePath(),
        });
        // do stuff to make sure p finishes & capture output
    }

    /**
     * 返回一个apk程序的信息。
     *
     * @param apkPath apk的路径。
     * @return apkInfo 一个Apk的信息。
     */
    public ApkInfo getApkInfo(String apkPath) throws Exception {
        Log.i("================================开始=========================");
        String osName = System.getProperty("os.name");
        String path = "";
        ApkInfo apkInfo = new ApkInfo();
        if (osName.toLowerCase(Locale.ROOT).startsWith("windows")) {
            path = mAaptPath;
        } else {
            path = mMacAaptPath;
            //mac需要权限
            Runtime.getRuntime().exec(new String[]{"chmod", "u" + ('+') + "x", path,});
        }
        Process process = mBuilder.command(path, "d", "badging", apkPath).start();
        InputStream is = null;
        Log.i("??? " + mBuilder.command());
        is = process.getInputStream();
        BufferedReader br = new BufferedReader(
                new InputStreamReader(is, "utf8"));
        String tmp = br.readLine();
        try {
            if (tmp == null || !tmp.startsWith("package")) {
                throw new Exception("参数不正确，无法正常解析APK包。输出结果为:\n" + tmp + "...");
            }
            apkInfo.setRsaPath("META-INF/KEY_KEYS.RSA");
            apkInfo.setChannelPath("assets/channel.txt");
            do {
                setApkInfoProperty(apkInfo, tmp);
            } while ((tmp = br.readLine()) != null);
            return apkInfo;
        } catch (Exception e) {
            throw e;
        } finally {
            process.destroy();
            closeIO(is);
            closeIO(br);
            getApkCertInfo(apkInfo, apkPath);
            Log.i("================================完成=========================");
        }
    }

    public void getApkCertInfo(ApkInfo info, String path) throws IOException {
        Log.i("============================getApkCertInfo=========================");
        ProcessBuilder mBuilder = new ProcessBuilder();
        mBuilder.redirectErrorStream(true);
        String osName = System.getProperty("os.name");
        String runPath = "";
        if (osName.toLowerCase(Locale.ROOT).startsWith("windows")) {
            runPath = "src/main/resources/lib/mac/apksigner.bat";
        } else {
            runPath = "src/main/resources/lib/mac/apksigner";
            //mac需要权限
            Runtime.getRuntime().exec(new String[]{"chmod", "u" + ('+') + "x", path,});
        }
        Process process = mBuilder.command(runPath, "verify", "--print-certs", path).start();
        InputStream is = null;
        is = process.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
        String tmp = br.readLine();
        boolean isNextRun = true;
        try {
            do {
                tmp = br.readLine();
                if (tmp != null) {
                    if (info.getSha256().isEmpty()) {
                        int s256 = tmp.indexOf("SHA-256");
                        if (s256 > -1) {
                            info.setSha256(tmp.substring(tmp.indexOf(":", s256) + 1));
                        }
                    }
                    if (info.getSha1().isEmpty()) {
                        int s1 = tmp.indexOf("SHA-1");
                        if (s1 > -1) {
                            info.setSha1(tmp.substring(tmp.indexOf(":", s1) + 1));
                        }
                    }
                    if (info.getMd5().isEmpty()) {
                        int md5 = tmp.indexOf("MD5");
                        if (md5 > -1) {
                            info.setMd5(tmp.substring(tmp.indexOf(":", md5) + 1));
                        }
                    }
                    if (!(info.getMd5().isEmpty() | info.getSha256().isEmpty() | info.getSha1().isEmpty())) {
                        isNextRun = false;
                    }
                } else {
                    isNextRun = false;
                }
            } while (isNextRun);
        } catch (Exception e) {
            Log.i("错误错误");
        } finally {
            process.destroy();
            closeIO(is);
            closeIO(br);
            Log.i(info.toString());
            Log.i("================================完成=========================");
        }
    }

    /**
     * 设置APK的属性信息。
     */
    private void setApkInfoProperty(ApkInfo apkInfo, String source) {
        if (source.startsWith(PACKAGE)) {
            splitPackageInfo(apkInfo, source);
        } else if (source.startsWith(LAUNCHABLE_ACTIVITY)) {
            apkInfo.setLaunchableActivity(getPropertyInQuote(source));
        } else if (source.startsWith(SDK_VERSION)) {
            apkInfo.setSdkVersion(getPropertyInQuote(source));
        } else if (source.startsWith(TARGET_SDK_VERSION)) {
            apkInfo.setTargetSdkVersion(getPropertyInQuote(source));
        } else if (source.startsWith(USES_PERMISSION)) {
            apkInfo.addToUsesPermissions(getPropertyInQuote(source));
        } else if (source.startsWith(APPLICATION_LABEL)) {
            //windows下获取应用名称
            apkInfo.setAppName(getPropertyInQuote(source));
        } else if (source.startsWith(APPLICATION_ICON)) {
            apkInfo.addToApplicationIcons(getKeyBeforeColon(source),
                    getPropertyInQuote(source));
        } else if (source.startsWith(APPLICATION)) {
            String[] rs = source.split("( icon=')|'");
            String iPath = rs[rs.length - 1];
//            apkInfo.setAppIcon(iPath.substring(iPath.indexOf(File.pathSeparator)+1,iPath.length()));
//            apkInfo.setAppIcon(iPath.substring(iPath.indexOf("/")+1,iPath.length()));
            apkInfo.setAppIcon(iPath);
            //linux下获取应用名称
            apkInfo.setAppName(rs[1]);
        } else if (source.startsWith(USES_FEATURE)) {
            apkInfo.addToFeatures(getPropertyInQuote(source));
        } else if (source.startsWith(USES_IMPLIED_FEATURE)) {
            apkInfo.addToImpliedFeatures(getFeature(source));
        } else if (source.startsWith(nativeCode)) {
            apkInfo.setNativeCodes(getPropertyInQuoteAll(source));
        } else {
            System.out.println(source);
        }
    }

    private ImpliedFeature getFeature(String source) {
        String[] result = source.split(FEATURE_SPLIT_REGEX);
        ImpliedFeature impliedFeature = new ImpliedFeature(result[1], result[2]);
        return impliedFeature;
    }

    /**
     * 返回出格式为name: 'value'中的value内容。
     */
    private String getPropertyInQuote(String source) {
        int index = source.indexOf("'") + 1;
        return source.substring(index, source.indexOf('\'', index));
    }

    /**
     * 返回出格式为name: 'value' 'saa' 'bbbb'中的所有内容
     */
    private List<String> getPropertyInQuoteAll(String source) {
        int index = source.indexOf("'") + 1;
        List<String> results = new ArrayList<>();
        String[] split = source.split(" ");
        for (int i = 0; i < split.length; i++) {
            String value = split[i];
            if (i != 0) {
                results.add(value.replace("'", ""));
            }
        }
        return results;
    }

    /**
     * 返回冒号前的属性名称
     */
    private String getKeyBeforeColon(String source) {
        return source.substring(0, source.indexOf(':'));
    }

    /**
     * 分离出包名、版本等信息。
     */
    private void splitPackageInfo(ApkInfo apkInfo, String packageSource) {
        String[] packageInfo = packageSource.split(SPLIT_REGEX);
        apkInfo.setPackageName(packageInfo[2]);
        apkInfo.setVersionCode(packageInfo[4]);
        apkInfo.setVersionName(packageInfo[6]);
    }

    /**
     * 释放资源。
     */
    public static final void closeIO(Closeable c) {
        if (c != null) {
            try {
                c.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public List<String> readFileLine(String path) {
        File file = new File(path);
        List<String> strings = new ArrayList<>();
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader(file, StandardCharsets.UTF_8));
            String tmp = br.readLine();
            if (tmp == null) {
                Log.i("读取不到文件");
            }
            do {
                if (tmp != null && !tmp.trim().isBlank()) {
                    strings.add(tmp.trim());
                }
            } while ((tmp = br.readLine()) != null);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeIO(br);
        }
        return strings;
    }

    public static String getSHA(String filePath, String messageDigestAlgorithms) {
        String hex = null;
        try {
            File file = new File(filePath);
            hex = new DigestUtils(messageDigestAlgorithms).digestAsHex(file);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return hex;
    }

    /**
     * 下面是另一种写法
     *
     * @param apkPath
     * @return
     */
    public static String getApkMD5(String apkPath) {
        return getSHAInfo(apkPath, "SHA-1");
    }

    public static String getApkSHA1(String apkPath) {
        return getSHAInfo(apkPath, "MD5");
    }

    public static String getApkSHA256(String apkPath) {
        return getSHAInfo(apkPath, "SHA-256");
    }

    public static String getSHAInfo(String apkPath, String kind) {
        try {
            MessageDigest dexDigest = MessageDigest.getInstance(kind);
            byte[] bytes = new byte[1024];
            int byteCount;
            FileInputStream fis = new FileInputStream(apkPath); // 读取apk文件
            while ((byteCount = fis.read(bytes)) != -1) {
                dexDigest.update(bytes, 0, byteCount);
            }
            BigInteger bigInteger = new BigInteger(1, dexDigest.digest()); // 计算apk文件的哈希值
            String sha = bigInteger.toString(16);
            fis.close();
            Log.i("sha =" + sha);
            return sha;
        } catch (NoSuchAlgorithmException | IOException e) {
            e.printStackTrace();
        }
        return "";
    }

}
